Skip to content

struct的占用字节数

在 C 语言中,struct 的占用字节数不仅取决于各个成员类型的大小,还会受到 内存对齐(alignment) 的影响。下面我详细解释计算方法。

image-20250910205912618

指针

指针的大小

一般指针的大小取决于系统总线地址宽度,32位系统的指针占4个字节,64位系统的指针占用的是8字节

指针的自增

指针自增自增多少个字节?取决于该变量的类型,该变量的类型占用几个字节,那么就自增几个字节

为什么声明要放在 .h文件

  • .h 文件的作用就是让别人知道我有哪些接口/变量可以用
  • 如果声明写在 .h 里,别的 .c 文件只要 #include "Led.h" 就能用,不需要每个 .c 文件自己去写一次 extern
  • 这样保证了变量声明唯一来源,不容易写错名字或类型

宏定义

宏定义不能以分号结尾!!!

十六进制输出

c
%x → 小写字母,十六进制,不带 0x,不补零

%X → 大写字母,十六进制,不带 0x,不补零

%08X → 固定 8 位,前面补 0

手动写 "0x%08X" → 带上前缀

大端小端是什么意思

1. 大端(Big Endian)和小端(Little Endian)是什么

它们指的是多字节数据(比如 intshortfloat)在内存中的存放顺序

  • 大端模式(Big Endian):高字节存放在低地址,低字节存放在高地址。
    • 内存从小到大排列:高字节 → … → 低字节
    • 类似我们写数字的方式:最高位在最前
  • 小端模式(Little Endian):低字节存放在低地址,高字节存放在高地址。
    • 内存从小到大排列:低字节 → … → 高字节
    • 就像把数字倒着存:最低位在最前

2. 举个例子

假设有个 32 位整数:

int a = 0x12345678;

内存地址从低到高:

  • 大端模式
地址: 0x00   0x01   0x02   0x03
数据: 0x12   0x34   0x56   0x78
  • 小端模式
地址: 0x00   0x01   0x02   0x03
数据: 0x78   0x56   0x34   0x12

3. 为什么会有大端和小端

  • 大端:更符合人类阅读习惯(高位在前),很多网络协议规定使用大端,所以也叫 网络字节序
  • 小端:硬件实现简单,x86 系列 CPU 默认就是小端。

函数相关

ssanf

sscanf 是 C 标准库中一个非常实用的字符串解析函数,用于从字符串中按照指定格式提取数据,类似于 scanf 从标准输入读取,但 sscanf 是从**字符数组(字符串)**中读取。

函数原型

c
#include <stdio.h>

int sscanf(const char *str, const char *format, ...); //返回值为解析了几个项目

测试程序

c
#include <stdio.h>

int main() {
    char str[] = "123 45.67";
    int a;
    float b;

    int ret = sscanf(str, "%d %f", &a, &b);
    printf("解析了 %d 个项目\n", ret); // 输出:2
    printf("a = %d, b = %.2f\n", a, b); // 输出:a = 123, b = 45.67

    return 0;
}

sprintf函数

sprintf 的第一个参数必须是 目标缓冲区(写入字符串的地方),第二个参数才是格式化字符串。

snprintf

限制拷贝多少个字符

c
// 限制版本号最多拷贝 31 个字符,确保结尾有 '\0'
  snprintf(temp, sizeof(temp),
             "{\"id\":\"1\",\"params\":{\"version\":\"%.*s\"}}",
             26, OTA_Info.OTA_ver);

sizeof

c
#include <stdio.h>
#include <string.h>
int main()
{
  int buffer[64];
  printf("sizeof(buffer) = %zu 字节\n", sizeof(buffer));
  printf("sizeof(int)     = %zu 字节\n", sizeof(int));
  printf("元素个数        = %zu\n", sizeof(buffer) / sizeof(buffer[0]));
  return 0;
}
sizeof(buffer) = 256 字节
sizeof(int)     = 4 字节
元素个数        = 64

memset

函数原型

c
#include <string.h>

void *memset(void *s, int c, size_t n);

假设我们定义了一个数组,然后直接输出,看看会怎么样

代码

c
#include <stdio.h>
#include <string.h>
int main()
{
  int buffer[64];
  for (int i = 0; i < sizeof(buffer)/sizeof(int); i++)
  {
    printf("%d ", buffer[i]);
  }
  return 0;
}

下面是输出的结果

0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2033488 0 256 0 2033496 0 8 0 2055568 0 -1571362666 32761 4205848 0 0 0 2033488 0 256 0 2033504 0 16 0 2055568 0 -1571362666 32761 8 0 0 0 2055600 0 4199920 0 0 0 0 0 8 0 4199705 0 8 0 23 0

如果我们想让他们全部置为0,可以按下面这样操作

c
memset(buffer, 0, 64 * sizeof(int));

注意:memset按字节设置内存,不适合设置非零 int 值(除非你明确知道字节布局)。

memcpy

memcpy 是 C 标准库中的一个非常重要的内存操作函数,用于从源内存地址复制指定字节数的数据到目标内存地址。它在 <string.h> 头文件中声明,是底层内存操作的核心函数之一,广泛用于数据拷贝、结构体复制、数组复制等场景。

和strcpy的区别是不会因为检测到\0就停止

测试程序

c
#include <stdio.h>
#include <string.h>
int main()
{
  int src[5] = {1, 2, 3, 4, 5};
  int dest[5];
  memcpy(dest, src, sizeof(src)); // 复制整个数组 4*5个字节
  for (int i = 0; i < 5; i++)
  {
    printf("%d ", dest[i]); // 输出:1 2 3 4 5
  }

  return 0;
}

伪随机数生成器

增加简单 PRNG 函数:

c
// 简单的线性同余发生器
static uint32_t seed = 1234567;

static uint32_t SimpleRand(void) {
    seed = seed * 1664525 + 1013904223;
    return (seed >> 16) & 0x7FFF;
}

修改获取值代码:

c
int temp = 15 + (SimpleRand() % 21);    // 温度值: 15–35 ℃
int hum = 30 + (SimpleRand() % 61);     // 湿度值: 30–90 %
int light = SimpleRand() % 1001;         // 光照值: 0–1000 Lux
int conc = 10 + (SimpleRand() % 51);    // 土壤湿度值: 10–60

关键字

volatile 关键字

volatile

告诉编译器: 这个变量的值随时可能发生变化,不要对它做优化,每次都要从内存重新读取,而不是用寄存器缓存。

使用场景

中断与主循环共享的变量

  • 例:中断里 flag=1;,主循环里检查 if(flag)

硬件寄存器

  • STM32 的外设寄存器值可能会被硬件改,比如 USARTx->SR,要加 volatile

多任务环境(RTOS)

  • 如果任务 A 改一个变量,任务 B 读取,也建议加。

extern 关键字

  • 作用:声明变量或函数在别的文件定义,告诉编译器“这个东西是外部的”,不分配内存,只用来引用。

  • 场景:跨文件访问全局变量时,在使用的 .c 文件里用 extern 声明变量,真正定义在另一个 .c 文件里。

  • .h 文件中用 extern 声明一个在 .c 文件中定义的全局变量,是为了将该变量的接口暴露给外部模块使用,相当于“导出”这个变量的访问权限。